<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>MNIST手写数字识别</title>
    <style>
        body {
            font-family: 'Arial', sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
            background-color: #f5f5f5;
        }

        h1 {
            color: #333;
            text-align: center;
        }

        .container {
            background-color: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
        }

        /* 手绘区域样式 */
        .drawing-area {
            height: 100%;
            width: 50%;
            text-align: center;
        }

        #drawingCanvas {
            border: 2px solid #ccc;
            border-radius: 5px;
            background-color: white;
            cursor: crosshair;
        }

        .drawing-controls {
            margin-top: 10px;
        }

        .drawing-controls button {
            margin: 0 5px;
            padding: 5px 10px;
            background-color: #4CAF50;
            color: white;
            border: none;
            border-radius: 3px;
            cursor: pointer;
        }

        /* 其他原有样式保持不变 */
        .single-line {
            display: flex;
            justify-content: space-between;
            align-items: center;
            height: 400px;
        }

        /* ... 其他原有样式 ... */


        .upload-area {
            border: 2px dashed #ccc;
            text-align: center;
            margin-bottom: 20px;
            border-radius: 5px;
            cursor: pointer;
            transition: border-color 0.3s;
        }

        .upload-area:hover {
            border-color: #999;
        }

        .upload-area.active {
            border-color: #4CAF50;
            background-color: #f0f8ff;
        }

        #preview {
            max-width: 280px;
            margin: 10px auto;
            display: block;
        }

        .result {
            background-color: #f0f8ff;
            border-radius: 5px;
            text-align: center;
            padding: 0 15px;
        }

        .history {
            margin-top: 30px;
        }

        .history-item {
            display: flex;
            align-items: center;
            margin-bottom: 10px;
            padding: 10px;
            background-color: #fff;
            border-radius: 5px;
            box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
        }

        .history-img {
            width: 50px;
            height: 50px;
            margin-right: 15px;
            object-fit: contain;
        }

        .history-info {
            flex-grow: 1;
        }

        .confidence-bar {
            height: 10px;
            background-color: #e0e0e0;
            border-radius: 5px;
            overflow: hidden;
            width: 200px;
            padding: 5px;
            margin: 16px 0;
        }

        .confidence-level {
            height: 10px;
            background-color: #4CAF50;
            width: 0%;
        }

        button {
            background-color: #4CAF50;
            color: white;
            border: none;
            padding: 10px 15px;
            border-radius: 4px;
            cursor: pointer;
            font-size: 16px;
            margin-bottom: 10px;
        }

        button:hover {
            background-color: #45a049;
        }

        .loading {
            text-align: center;
            height: 114.58px;
        }

        #fileInputLabel {
            display: inline-block;
            padding: 10px 15px;
            background-color: #4CAF50;
            color: white;
            border-radius: 4px;
            cursor: pointer;
        }

        #fileInputLabel:hover {
            background-color: #45a049;
        }
    </style>
</head>

<body>
    <div class="container">
        <h1>MNIST手写数字识别</h1>
        <div class="single-line">
            <!-- 新增的手绘区域 -->
            <div class="drawing-area">
                <h2>手写数字识别</h2>
                <canvas id="drawingCanvas" width="280" height="280"></canvas>
                <div class="drawing-controls">
                    <button id="clearCanvas">清除</button>
                    <button id="recognizeDrawing">识别</button>
                </div>
            </div>

            <!-- 原有的上传区域 -->
            <div class="drawing-area">
                <h2>上传图片识别</h2>
                <div class="upload-area" id="uploadArea" style="height: 280px;">
                    <input type="file" id="imageUpload" accept="image/*" title="点击或拖拽上传图片" style="display: none;">
                    <p>或拖拽图片到此处</p>
                    <img id="preview" alt="待识别的图片" style="display: none;">
                </div>
            </div>
        </div>

        <!-- 其他原有内容保持不变 -->
        <div class="loading result" id="loading">
            <p>准备识别...</p>
        </div>

        <div class="result" id="result" style="display: none;">
            <h2>识别结果</h2>
            <div style="display: flex;justify-content: space-between;">
                <p>预测数字: <span id="predictionResult" style="font-weight: bold; font-size: 24px;"></span></p>
                <p>置信度: <span id="confidenceValue"></span>%</p>
                <div class="confidence-bar">
                    <div class="confidence-level" id="confidenceBar"></div>
                </div>
                <p id="resultTime"></p>
            </div>
        </div>

        <div class="history">
            <h2>识别历史</h2>
            <button onclick="clearHistory()">清空历史</button>
            <div id="historyList"></div>
        </div>
    </div>

    <script>
        // ========== 手绘功能实现 ==========
        const canvas = document.getElementById('drawingCanvas');
        const ctx = canvas.getContext('2d');
        let isDrawing = false;
        let isRecognized = false;
        let mouseRect = {
            xmin: Number.MAX_VALUE,
            xmax: Number.MIN_VALUE,
            ymin: Number.MAX_VALUE,
            ymax: Number.MIN_VALUE,
        }

        // 设置画笔样式
        clearCanvas(ctx)

        // 开始绘制
        canvas.addEventListener('mousedown', startDrawing);
        canvas.addEventListener('touchstart', startDrawing);

        // 绘制中
        canvas.addEventListener('mousemove', draw);
        canvas.addEventListener('touchmove', draw);

        // 结束绘制
        canvas.addEventListener('mouseup', stopDrawing);
        canvas.addEventListener('mouseout', stopDrawing);
        canvas.addEventListener('touchend', stopDrawing);

        // 清除画布
        document.getElementById('clearCanvas').addEventListener('click', clearCanvas);

        // 识别手绘数字
        document.getElementById('recognizeDrawing').addEventListener('click', recognizeDrawing);

        function startDrawing(e) {
            if (isRecognized) {
                clearCanvas(); // 每次开始绘制时清除画布
                isRecognized = false;
            }
            isDrawing = true;
            draw(e); // 立即绘制一个点
        }

        function draw(e) {
            if (!isDrawing) return;

            // 获取鼠标/触摸位置
            const rect = canvas.getBoundingClientRect();
            let clientX, clientY;

            if (e.type.includes('touch')) {
                clientX = e.touches[0].clientX;
                clientY = e.touches[0].clientY;
            } else {
                clientX = e.clientX;
                clientY = e.clientY;
            }

            const x = clientX - rect.left;
            const y = clientY - rect.top;

            mouseRect.xmin = Math.min(mouseRect.xmin, x);
            mouseRect.ymin = Math.min(mouseRect.ymin, y);
            mouseRect.xmax = Math.max(mouseRect.xmax, x);
            mouseRect.ymax = Math.max(mouseRect.ymax, y);

            ctx.lineTo(x, y);
            ctx.stroke();
            ctx.beginPath();
            ctx.moveTo(x, y);
        }

        function stopDrawing() {
            isDrawing = false;
            ctx.beginPath(); // 开始新的路径
        }

        function clearCanvas() {

            // 填充黑色背景
            ctx.fillStyle = '#000000';
            ctx.fillRect(0, 0, canvas.width, canvas.height);

            // 重置画笔样式
            ctx.strokeStyle = '#FFFFFF'; // 白色画笔
            ctx.lineWidth = 5;
            ctx.lineCap = 'round';
            ctx.beginPath();

            mouseRect = {
                xmin: Number.MAX_VALUE,
                xmax: Number.MIN_VALUE,
                ymin: Number.MAX_VALUE,
                ymax: Number.MIN_VALUE,
            }
        }

        /**
         * 裁剪 Canvas 的指定区域
         * @param {HTMLCanvasElement} canvas - 源Canvas元素
         * @param {number} x - 裁剪起点X坐标
         * @param {number} y - 裁剪起点Y坐标 
         * @param {number} width - 裁剪宽度
         * @param {number} height - 裁剪高度
         * @returns {HTMLCanvasElement} 新Canvas元素
         */
        function cropCanvas(canvas, x, y, width, height) {
            // 创建新Canvas
            const croppedCanvas = document.createElement('canvas');
            croppedCanvas.width = width;
            croppedCanvas.height = height;

            // 获取上下文并绘制裁剪区域
            const ctx = croppedCanvas.getContext('2d');
            // 填充黑色背景
            ctx.fillStyle = '#000000';
            ctx.fillRect(0, 0, croppedCanvas.width, croppedCanvas.height);

            ctx.drawImage(
                canvas,
                x, y, width, height,  // 源图像裁剪区域
                0, 0, width, height    // 目标Canvas绘制区域
            );

            return croppedCanvas;
        }

        async function recognizeDrawing() {

            // 显示加载状态
            loadingDiv.style.display = 'block';
            resultDiv.style.display = 'none';

            try {

                let width = mouseRect.xmax - mouseRect.xmin;
                let height = mouseRect.ymax - mouseRect.ymin;
                let cropedCanvas = canvas;
                if (width > 0 && Math.max(width, height) * 2 < canvas.width) {
                    let x = mouseRect.xmin;
                    let y = mouseRect.ymin;

                    if (width > height) {
                        const dh = (width - height) / 2;
                        y -= dh;
                        height = width;
                    }
                    else {
                        const dw = (height - width) / 2;
                        x -= dw;
                        width = height;
                    }

                    const offset = width * 0.1;
                    x -= offset;
                    y -= offset * 2;
                    width += offset * 2;
                    height += offset * 2;

                    cropedCanvas = cropCanvas(canvas, x, y, width, height);
                    if (!cropedCanvas) {
                        cropedCanvas = canvas;
                    }
                }


                // 将画布内容转换为Blob
                cropedCanvas.toBlob(async (blob) => {

                    const formData = new FormData();
                    formData.append('file', blob, 'drawing.png');

                    const response = await fetch('http://127.0.0.1:5001/predict', {
                        method: 'POST',
                        body: formData,
                        headers: {
                            'Accept': 'application/json'
                        }
                    });

                    if (!response.ok) throw new Error(`HTTP错误: ${response.status}`);

                    const result = await response.json();
                    console.log('识别结果:', result);

                    // 显示结果
                    displayResult(result, cropedCanvas.toDataURL());

                    // 保存到历史记录
                    saveToHistory(result, cropedCanvas.toDataURL());

                    resultDiv.style.display = 'block';
                    loadingDiv.style.display = 'none';
                    isRecognized = true;
                }, 'image/png');
            } catch (error) {
                console.error('识别失败:', error);
                resultDiv.innerHTML = `<p style="color: red;">识别失败: ${error.message}</p>`;
                resultDiv.style.display = 'block';
                loadingDiv.style.display = 'none';
            }
        }

        // ========== 原有功能保持不变 ==========
        // DOM元素
        const uploadArea = document.getElementById('uploadArea');
        const fileInput = document.getElementById('imageUpload');
        const previewImg = document.getElementById('preview');
        const resultDiv = document.getElementById('result');
        const loadingDiv = document.getElementById('loading');
        const historyList = document.getElementById('historyList');

        // 点击上传区域触发文件选择
        uploadArea.addEventListener('click', (e) => {
            fileInput.click();
        });

        // 拖拽功能
        uploadArea.addEventListener('dragover', (e) => {
            e.preventDefault();
            uploadArea.classList.add('active');
        });

        uploadArea.addEventListener('dragleave', () => {
            uploadArea.classList.remove('active');
        });

        uploadArea.addEventListener('drop', (e) => {
            e.preventDefault();
            uploadArea.classList.remove('active');

            if (e.dataTransfer.files.length) {
                fileInput.files = e.dataTransfer.files;
                handleFileUpload();
            }
        });

        // 文件选择变化时自动处理
        fileInput.addEventListener('change', handleFileUpload);

        // 处理文件上传和识别
        async function handleFileUpload() {
            const file = fileInput.files[0];
            if (!file) return;

            // 检查文件类型
            if (!file.type.match('image.*')) {
                alert('请上传图片文件');
                return;
            }

            // 显示预览
            const reader = new FileReader();
            reader.onload = (e) => {
                previewImg.src = e.target.result;
                previewImg.style.display = 'block';
            };
            reader.readAsDataURL(file);

            // 显示加载状态
            loadingDiv.style.display = 'block';
            resultDiv.style.display = 'none';

            try {
                const formData = new FormData();
                formData.append('file', file);

                const response = await fetch('http://127.0.0.1:5001/predict', {
                    method: 'POST',
                    body: formData,
                    headers: {
                        'Accept': 'application/json'
                    }
                });

                if (!response.ok) throw new Error(`HTTP错误: ${response.status}`);

                const result = await response.json();
                console.log('识别结果:', result);

                // 显示结果
                displayResult(result, previewImg.src);

                // 保存到历史记录
                saveToHistory(result, previewImg.src);

            } catch (error) {
                console.error('识别失败:', error);
                resultDiv.innerHTML = `<p style="color: red;">识别失败: ${error.message}</p>`;
                resultDiv.style.display = 'block';
            } finally {
                loadingDiv.style.display = 'none';
            }
        }

        // 显示识别结果
        function displayResult(result, imgSrc) {
            const prediction = result.prediction;
            const confidence = (result.confidence * 100).toFixed(2);
            const now = new Date();
            const timeString = now.toLocaleTimeString();

            document.getElementById('predictionResult').textContent = prediction;
            document.getElementById('confidenceValue').textContent = confidence;
            document.getElementById('confidenceBar').style.width = `${confidence}%`;
            document.getElementById('resultTime').textContent = `识别时间: ${timeString}`;

            resultDiv.style.display = 'block';
        }

        // 保存到历史记录
        function saveToHistory(result, imgSrc) {
            const history = JSON.parse(localStorage.getItem('mnistHistory')) || [];
            const newItem = {
                id: Date.now(),
                prediction: result.prediction,
                confidence: result.confidence,
                imgSrc: imgSrc,
                time: new Date().toLocaleString()
            };

            history.unshift(newItem); // 添加到开头
            if (history.length > 20) history.pop(); // 限制历史记录数量

            localStorage.setItem('mnistHistory', JSON.stringify(history));
            renderHistory();
        }

        // 渲染历史记录
        function renderHistory() {
            const history = JSON.parse(localStorage.getItem('mnistHistory') || '[]');
            historyList.innerHTML = '';

            if (history.length === 0) {
                historyList.innerHTML = '<p>暂无历史记录</p>';
                return;
            }

            history.forEach(item => {
                const historyItem = document.createElement('div');
                historyItem.className = 'history-item';

                historyItem.innerHTML = `
                    <img src="${item.imgSrc}" class="history-img">
                    <div class="history-info">
                        <div>数字: ${item.prediction} (${(item.confidence * 100).toFixed(1)}%)</div>
                        <div class="confidence-bar">
                            <div class="confidence-level" style="width: ${item.confidence * 100}%"></div>
                        </div>
                        <small>${item.time}</small>
                    </div>
                `;

                historyList.appendChild(historyItem);
            });
        }

        // 清空历史记录
        function clearHistory() {
            if (confirm('确定要清空所有历史记录吗？')) {
                localStorage.removeItem('mnistHistory');
                renderHistory();
            }
        }

        // 初始化页面时加载历史记录
        renderHistory();
    </script>
</body>

</html>